package me.zhengjie.modules.system.service.impl

import cn.hutool.core.util.ObjectUtil
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery
import jakarta.persistence.criteria.Root
import jakarta.servlet.http.HttpServletResponse
import me.zhengjie.common.exception.BadRequestException
import me.zhengjie.common.utils.*
import me.zhengjie.common.utils.FileUtil.downloadExcel
import me.zhengjie.common.utils.enums.DataScopeEnum
import me.zhengjie.modules.system.domain.Dept
import me.zhengjie.modules.system.domain.User
import me.zhengjie.modules.system.repository.DeptRepository
import me.zhengjie.modules.system.repository.RoleRepository
import me.zhengjie.modules.system.repository.UserRepository
import me.zhengjie.modules.system.service.DeptService
import me.zhengjie.modules.system.service.dto.DeptDTO
import me.zhengjie.modules.system.service.dto.DeptQueryCriteria
import me.zhengjie.modules.system.service.mapper.DeptMapper
import org.springframework.cache.annotation.CacheConfig
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import org.springframework.util.CollectionUtils
import java.io.IOException
import java.util.function.Consumer
import java.util.stream.Collectors

/**
 * @author Kuki Wu
 * @date 2019-03-25
 */
@Service
@CacheConfig(cacheNames = ["dept"])
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = [Exception::class])
class DeptServiceImpl(
    private val deptRepository: DeptRepository,
    private val deptMapper: DeptMapper,
    private val userRepository: UserRepository? = null,
    private val redisUtils: RedisUtil? = null,
    private val roleRepository: RoleRepository? = null
) : DeptService {

    //    override fun queryAll(criteria: DeptQueryCriteria): List<DeptDTO> {
//        return deptMapper.toDto(deptRepository.findAll { root: Root<Dept?>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
//            QueryHelp.getPredicate(
//                root,
//                criteria,
//                criteriaBuilder
//            )
//        }) as List<DeptDTO>
//    }
@Throws(java.lang.Exception::class)
override fun queryAll(criteria: DeptQueryCriteria, isQuery: Boolean): List<DeptDTO?>? {
    val sort = Sort.by(Sort.Direction.ASC, "deptSort")
    val dataScopeType: String = SecurityUtils.getDataScopeType()
    if (isQuery) {
        if (dataScopeType == DataScopeEnum.ALL.value) {
            criteria.pidIsNull =true
        }
        val fields = QueryHelp.getAllFields(criteria.javaClass, java.util.ArrayList())
        val fieldNames: List<String> = arrayListOf("pidIsNull", "enabled")

        for (field in fields) {
            //设置对象的访问权限，保证对private的属性的访问
            field.isAccessible = true
            val `val` = field[criteria]
            if (fieldNames.contains(field.name)) {
                continue
            }
            if (ObjectUtil.isNotNull(`val`)) {
                criteria.pidIsNull  = null
                break
            }
        }
    }
    val list: List<DeptDTO?>? =
        deptMapper.toDto(deptRepository.findAll({ root: Root<Dept?>?, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder? ->
            QueryHelp.getPredicate(
                root!!, criteria,
                criteriaBuilder!!
            )
        }, sort))
    // 如果为空，就代表为自定义权限或者本级权限，就需要去重，不理解可以注释掉，看查询结果
    return if (org.apache.commons.lang3.StringUtils.isBlank(dataScopeType)) {
        deduplication(list!! as List<DeptDTO>)
    } else list
}

    @Cacheable(key = "'findbyid'+#p0")
    override fun findById(id: Long): DeptDTO? {
        val dept = deptRepository.findById(id).orElseGet { Dept() }
        ValidationUtil.isNull(dept!!.id, "Dept", "id", id)
        return deptMapper.toDto(dept)
    }

    @Cacheable(key = "'findbypid'+#p0")
    override fun findByPid(pid: Long): List<Dept> {
        return deptRepository.findByPid(pid) as List<Dept>
    }

    @Cacheable(key = "'findbyrolesids'+#p0")
    override fun findByRoleIds(id: Long): Set<Dept?>? {
        return deptRepository.findByRoles_Id(id)!!.map { obj: Dept? -> obj }.toSet()
    }

    @Cacheable(key = "'buildtree'")
    override fun buildTree(deptDTOS: List<DeptDTO>): Any {
        var trees: MutableSet<DeptDTO?> = LinkedHashSet()
        val depts: MutableSet<DeptDTO?> = LinkedHashSet()
        val deptNames = deptDTOS.stream().map { obj: DeptDTO -> obj.name!! }.collect(Collectors.toList())
        var isChild: Boolean
        for (deptDTO in deptDTOS) {
            deptDTO.label = deptDTO.name
            isChild = false
            if ("0" == deptDTO.pid.toString()) {
                trees.add(deptDTO)
            }
            for (it in deptDTOS) {
                if (it.pid!! == deptDTO.id!!) {
                    isChild = true
                    if (deptDTO.children == null) {
                        deptDTO.children = LinkedHashSet()
                    }
                    deptDTO.children!!.add(it)
                }
            }
            if (isChild) depts.add(deptDTO) else if (!deptNames.contains(deptRepository.findNameByIdNotNull(deptDTO.pid))) depts.add(
                deptDTO
            )
        }
        if (CollectionUtils.isEmpty(trees)) {
            trees = depts
        }
        val totalElements = deptDTOS.size
        val map: MutableMap<String, Any> = HashMap()
        map["totalElements"] = totalElements
        map["content"] = if (CollectionUtils.isEmpty(trees)) deptDTOS else trees
        return map
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun create(resources: Dept): DeptDTO? {
        return deptMapper.toDto(deptRepository.save(resources))
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun update(resources: Dept) {
        if (resources.id == resources.pid) {
            throw BadRequestException("上级不能为自己")
        }
        val dept = deptRepository.findById(resources.id!!).orElseGet { Dept() }
        ValidationUtil.isNull(dept!!.id, "Dept", "id", resources.id!!)
        resources.id = dept.id
        deptRepository.save(resources)
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun delete(id: Long) {
        deptRepository.deleteById(id)
    }
    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun delete(deptDTOs:Set<DeptDTO>) {
        for(deptdto in deptDTOs) {
            delCaches(deptdto.id!!)
            deptRepository.deleteById(deptdto.id!!)
            updateSubCnt(deptdto.pid)
        }
    }

    @Throws(IOException::class)
    override fun download(deptDTOs: List<DeptDTO>, response: HttpServletResponse?) {
        val list: MutableList<Map<String?, Any?>?> = ArrayList()
        for (deptDTO in deptDTOs) {
            val map: MutableMap<String?, Any?> = LinkedHashMap()
            map["部门名称"] = deptDTO.name
            map["部门状态"] = if (deptDTO.enabled!!) "启用" else "停用"
            list.add(map)
        }
        downloadExcel(list, response!!)
    }

    override fun getDeleteDepts(menuList: List<Dept?>?, deptDTOs: MutableList<DeptDTO?>?): Set<DeptDTO?>? {
        for (dept in menuList!!) {
            deptDTOs!!.add(deptMapper.toDto(dept))
            val depts = deptRepository.findByPid(dept!!.id)
            if (depts != null && depts.size != 0) {
                getDeleteDepts(depts, deptDTOs)
            }
        }
        return deptDTOs!!.toSet()
    }

    override fun getDeptChildren(deptList: List<Dept?>?): List<Long?>? {
        val list: MutableList<Long?> = java.util.ArrayList()
        deptList!!.forEach(
            Consumer { dept: Dept? ->
                if (dept != null && dept.enabled!!) {
                    val depts = deptRepository.findByPid(dept.id)
                    if (depts!!.size != 0) {
                        list.addAll(getDeptChildren(depts)!!)
                    }
                    list.add(dept.id)
                }
            }
        )
        return list
    }

    override fun getSuperior(deptDTO: DeptDTO?, depts: MutableList<Dept?>?): List<DeptDTO?>? {
        if (deptDTO!!.pid == null) {
            depts!!.addAll(deptRepository.findByPidIsNull()!!)
            return deptMapper.toDto(depts)
        }
        depts!!.addAll(deptRepository.findByPid(deptDTO.pid)!!)
        return getSuperior(findById(deptDTO.pid!!), depts)
    }

    override fun verification(deptDTOs: Set<DeptDTO?>?) {
        //val deptIds = deptDTOs!!.stream().map<Any?>(DeptDto::getId).collect(Collectors.toSet<Any?>())
        val deptIds = deptDTOs!!.map { it!!.id }.toSet()
        if (userRepository!!.countByDepts(deptIds) > 0) {
            throw BadRequestException("所选部门存在用户关联，请解除后再试！")
        }
        if (roleRepository!!.countByDepts(deptIds) > 0) {
            throw BadRequestException("所选部门存在角色关联，请解除后再试！")
        }
    }

    private fun updateSubCnt(deptId: Long?) {
        if (deptId != null) {
            val count: Int = deptRepository.countByPid(deptId)
            deptRepository.updateSubCntById(count, deptId)
        }
    }

    private fun deduplication(list: List<DeptDTO>): List<DeptDTO?> {
        val deptDtos: MutableList<DeptDTO> = java.util.ArrayList<DeptDTO>()
        for (deptDto in list) {
            var flag = true
            for (dto in list) {
                if (dto.id!!.equals(deptDto.pid)) {
                    flag = false
                    break
                }
            }
            if (flag) {
                deptDtos.add(deptDto)
            }
        }
        return deptDtos
    }

    /**
     * 清理缓存
     * @param id /
     */
    fun delCaches(id: Long) {
        val users: List<User?> = userRepository!!.findByRoleDeptId(id)!!
        // 删除数据权限
        redisUtils!!.delByKeys(CacheKey.DATA_USER, users.map { it!!.id }.toSet())
        redisUtils.del(CacheKey.DEPT_ID + id)
    }
}